/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package tetris_invader.util;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JPanel;
import tetris_invader.itens.Beam;
import tetris_invader.itens.Energy_Item;
import tetris_invader.itens.InvaderBeam;
import tetris_invader.itens.Item;
import tetris_invader.itens.Life_Item;
import tetris_invader.itens.PieceFactory;
import tetris_invader.itens.PieceFactory.PieceType;
import tetris_invader.itens.SpecialBeam;
import tetris_invader.personagens.Explode;
import tetris_invader.personagens.Fabrica;
import tetris_invader.personagens.Fabrica.TipoPersonagem;
import tetris_invader.personagens.Invasor;
import tetris_invader.personagens.Invasor_Easy;
import tetris_invader.personagens.Invasor_Hard;
import tetris_invader.personagens.Invasor_Medium;
import tetris_invader.personagens.Nave;
import tetris_invader.personagens.Personagem;
import tetris_invader.windows.Frame;
import tetris_invader.windows.InfoPanel;

/**
 * Classe da fase.<br>
 * Implementa JPanel por ser um painel, mesmo. Contem todas as informacoes de uma
 * fase, desde a quantidade de personagens que participam da cena ate os itens
 * que estao sendo utilizados ou ficam dispersos no tela, mesmo.
 */
public class Fase extends JPanel implements TimerListener, Serializable {

    /**
     * É a thread em si.
     */
    private transient Timer timer;
    /**
     * Grau de dificuldade da fase
     */
    private int level;
    /**
     * Quantidade de invasores que estarao na fase.<br>
     * A conta é feita em pesos pre definidos do grau de dificuldade de cada
     * classe de invasor
     */
    private int quant;
    /**
     * Array de personagem jogaveis.
     */
    private ArrayList<Nave> personagem;
    /**
     * Array de invasores.<br>
     * Contem todos os invasores da fase. Foi utilizado esse tipo de estrutura
     * por ser de facil manipulacao, desde acrescimos de personagens e sua remocao.
     */
    private ArrayList<Invasor> invasor;
    ArrayList<Invasor> array;
    private Frame frame;
    /**
     * Todos os itens que estao sendo utilizados na fase estao nesse array.<br>
     * Foi utilizado por ser de facil manipulacao, desde acrescimos de novos itens
     * ate sua remocao.
     */
    private ArrayList<Item> itens;
    /**
     * Painel com as informacoes do jogador na fase.
     */
    private InfoPanel info;
    /**
     *Contador de numero de invasores aniquilados, ja mortos pelo jogador
     */
    private int elim;
    private int elimAux;
    /**
     * ArrayList que contem a posicao dos invasores aniquilados, para fazer o
     * desenho da explosao
     */
    private ArrayList<Explode> explode;

    public Fase(Frame frame, Boolean mode) {
        this.level = 0;
        this.setBackground(Color.black);
        this.timer = new Timer(this, 100);
        this.personagem = new ArrayList<Nave>();
        this.invasor = new ArrayList<Invasor>();//FIXME mudar quantidade de invasores por fase
        this.frame = frame;
        itens = new ArrayList<Item>();
        this.setSize(frame.getSize());
        elim = 0;
        elimAux = 0;
        array = new ArrayList<Invasor>();
        explode = new ArrayList<Explode>();
        if (mode == true) {
            this.recover();
        }
    }

    public void start() {
        if (!timer.isAlive()) {
            timer.start();
        } else if (timer.isPause()) {
            timer.pause();
        }
    }

    public void save() {
        FileOutputStream log = null;
        try {
            log = new FileOutputStream("log.ta");
            ObjectOutputStream obj = new ObjectOutputStream(log);
            obj.writeInt(quant);
            obj.writeInt(elim);
            obj.writeObject(invasor);
            obj.writeInt(level);
        } catch (Exception ex) {
            System.out.println("Nao tá escrevendo!!!");
            Logger.getLogger(Fase.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                System.out.println("Salvo!!");
                log.close();
            } catch (IOException ex) {
                System.out.println("Nao deu pra fechar o stream de escrita :(");
                Logger.getLogger(Fase.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    public void recover() {
        System.out.println("Chamou pra recuperar!");
        FileInputStream log = null;
        ObjectInputStream obj = null;
        try {
            log = new FileInputStream("log.ta");
            obj = new ObjectInputStream(log);
            quant = obj.readInt();
            elim = obj.readInt();
            invasor = (ArrayList<Invasor>) obj.readObject();
            level = obj.readInt();
            for (Invasor i : invasor) {
                i.setFase(this);
            }
            this.start();

        } catch (Exception ex) {
            System.out.println("Nao tá lendo!!!");
            Logger.getLogger(Fase.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                log.close();
            } catch (IOException ex) {
                System.out.println("Nao deu pra fechar o stream de escrita :(");
                Logger.getLogger(Fase.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    public InfoPanel getInfoPanel() {
        return info;
    }

    public void setInfoPanel(InfoPanel info) {
        this.info = info;
    }

    public int getElim() {
        return elim;
    }

    public void setElim(int elim) {
        this.elim = elim;
    }

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    /**
     * Inicializador de todos os personagens que estao na tela.
     */
    public void inicializar(Boolean mode) {
        personagem.add((Nave) Fabrica.Factory(TipoPersonagem.NAVE, this));
        frame.addKeyListener(personagem.get(0));

        if (mode == false) {
            inicializaInvasor(level, TipoPersonagem.INVASOR_HARD);
            inicializaInvasor((level + 2), TipoPersonagem.INVASOR_MEDIUM);
            inicializaInvasor((level + 1) * 2, TipoPersonagem.INVASOR_EASY);

            invasor.addAll(array);

            for (int i = array.size() - 1; i >= 0; i--) {
                array.remove(i);
            }
        } else {
            System.out.println("Chamou na inicializar!");
            this.recover();
        }
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2 = (Graphics2D) g;

        //TODO desenha os itens
        for (Item i : itens) {
            i.desenha(g2);
        }

        //TODO desenha peças

        for (Personagem p : personagem) {
            p.desenha(g2);
        }
        for (Invasor p : invasor) {
            p.desenha(g2);
        }
        for (int k = explode.size() - 1; k >= 0; k--) {
            if (explode.get(k).isFlag()) {
                explode.get(k).desenha(g2, this);
            } else {
                explode.get(k).stop();
                explode.remove(k);
            }
        }
    }

    /**
     * Atualiza todas as informacoes do conteudo da fase, desde o deslocamento
     * dos personagens e invasores até o surgimento de novos itens na tela.
     * tambem é feito a checagem de colisoes entre os invasores e itens.
     */
    public synchronized void update() {

        info.setLifes(personagem.get(0).getVida());
        info.setLevel(level);
        info.setEnergy(personagem.get(0).getEnergia());

        //Movimentacao do personagem
        naveUpdate();

        //Verificacao dos itens
        itemUpdate();

        //Movimentacao dos invasores e testes de colisoes
        invaderUpdate();

        //Mudanca de level
        levelUpdate();

        this.repaint();
    }

    /**
     * Adiciona invasores ao ArrayList de invasores, conforme a nivel da fase, numa
     * proporcao pre definida de invasores de niveis <i>easy, medium</i> e <i>hard</i>
     */
    public void inicializaInvasor(int quant, TipoPersonagem tipo) {
        Random r = new Random();
        Point p = new Point();
        int tam = array.size();

        for (int k = tam; k < quant + tam; k++) {
            do {
                p.setLocation(r.nextInt(570), -60);
            } while (hasObject(p));
            array.add((Invasor) Fabrica.Factory(tipo, this));

            array.get(k).setPosition(p);//new Point((k + 1) * 40, -60));
        }
    }

    /**
     * Funcao que verifica se na posicao onde esta o ponto ha algum objeto que ocupa esse lugar
     *
     * @param p Ponto com que se est'a comparando
     * @return <i>true</i> se houver algum objeto
     */
    public synchronized boolean hasObject(Point p) {

        for (Invasor i : array) {
            if (i.positionError(p)) {
                return true;
            }
        }
        return false;
    }

    /*
     * pausa a thread
     */
    public void pause() {
        timer.pause();
    }

    /**
     * Funcao responsavel pela eliminacao de todo o conteudo da fase.
     */
    public void stopFase() {
        timer.pause();
        for (int k = invasor.size() - 1; k
                >= 0; k--) {
            invasor.remove(k);
        }
        for (int k = itens.size() - 1; k
                >= 0; k--) {
            itens.remove(k);
        }
        for (int k = personagem.size() - 1; k
                >= 0; k--) {
            personagem.remove(k);
        }
        for (int k = array.size() - 1; k
                >= 0; k--) {
            array.remove(k);
        }
        level = 0;
        elim = 0;
        elimAux = 0;
        elimAux = 0;
    }

    /**
     * Funcao que dá reinício à fase. Basicamente elimina todos os invasores,
     * personagens e itens da tela.
     */
    public void restart() {
        if (!isPause()) {
            stopFase();
        }
    }

    /**
     * REtorna booleano dizendo se a fase está em pause, ou nao
     * @return se a fase esta em pause retorna true
     */
    public boolean isPause() {
        return timer.isPause();
    }

    /**
     * Basicamente é o deslocamento da nave e checagem de algum disparo realizado.<br>
     * A verificacao do tiro especial tambem é feita aqui.
     */
    private void naveUpdate() {
        for (Nave p : personagem) {
            p.deslocamento();
            p.setScore(info.getPoints());
            //Verificacao de tiros
            if (p.atira()) {
                itens.add((Beam) PieceFactory.fabricaPeca(this, PieceType.BEAM));
                int k = itens.size();
                itens.get(k - 1).setposicao(new Point(personagem.get(0).getPosition().x + 10, personagem.get(0).getPosition().y - 50));
            }
            if (p.special()) {
                int k = itens.size();
                itens.add((SpecialBeam) PieceFactory.fabricaPeca(this, PieceType.SPECIAL_BEAM));
                SpecialBeam b = (SpecialBeam) itens.get(k);
                b.start();
                itens.get(k).setposicao(new Point(personagem.get(0).getPosition().x - 50, personagem.get(0).getPosition().y));
            }
        }
    }

    /**
     * Verificador dos itens.<br>
     * Cada item da tela recebe um tratamento especial ou levemente parecido, por isso
     * essa funcao é bem grande. É verificada a colisao entre a nave, caso seja algum
     * item bonus, ou se for um tiro, seja de algum invasor ou da nave, é realizado o
     * teste de colisao.
     */
    private void itemUpdate() {
        for (int i = 0; i < itens.size(); i++) {
            itens.get(i).deslocamento();
            //teste da colisao do tiro
            if (itens.get(i) instanceof Beam) {
                for (int k = 0; k < invasor.size(); k++) {

                    if (itens.get(i).colisao(invasor.get(k))) {
                        if (invasor.get(k).perdeVida()) {
                            if (invasor.get(k) instanceof Invasor_Hard) {
                                info.addPoints(5);
                                personagem.get(0).addEnergia(7);
                            } else if (invasor.get(k) instanceof Invasor_Medium) {
                                info.addPoints(3);
                                personagem.get(0).addEnergia(5);
                            } else if (invasor.get(k) instanceof Invasor_Easy) {
                                info.addPoints(1);
                                personagem.get(0).addEnergia(3);
                            }
                            int aux = explode.size();
                            explode.add(new Explode());
                            explode.get(aux).setPosicao(invasor.get(k).getPosition());
                            explode.get(aux).start();

                            elim++;
                            invasor.remove(k);
                        }
                    }
                }
                //Se o tiro nao saiu da tela
                if (itens.get(i).offLimits()) {
                    personagem.get(0).subtractBeam();
                    itens.remove(i);
                }
            } else if (itens.get(i) instanceof SpecialBeam) {
                /*Tiro especial*/
                for (int k = 0; k < invasor.size(); k++) {

                    if (itens.get(i).colisao(invasor.get(k))) {
                        if (invasor.get(k).perdeVida()) {
                            if (invasor.get(k) instanceof Invasor_Hard) {
                                info.addPoints(5);
                            } else if (invasor.get(k) instanceof Invasor_Medium) {
                                info.addPoints(3);
                            } else if (invasor.get(k) instanceof Invasor_Easy) {
                                info.addPoints(1);
                            }
                            int aux = explode.size();
                            explode.add(new Explode());
                            explode.get(aux).setPosicao(invasor.get(k).getPosition());
                            explode.get(aux).start();

                            elim++;
                            invasor.remove(k);
                        }
                    }
                }
                for (int k = 0; k < itens.size(); k++) {

                    if (itens.get(i).colisao(itens.get(k))) {
                        if (k < i) {
                            i--;
                        }
                        itens.remove(k);
                    }
                }



            } /*Testando tiro dos invasores*/ else if (itens.get(i) instanceof InvaderBeam) {
                if (itens.get(i).colisao(personagem.get(0))) {
                    itens.remove(i);
                    if (personagem.get(0).perdeVida()) {
                        System.out.print(info.getPoints());
                        frame.GameOver(info.getPoints());
                    }
                }
            } else if (itens.get(i) instanceof Life_Item) {
                if (itens.get(i).itemDelay()) {
                    itens.remove(i);
                } else if (itens.get(i).colisao(personagem.get(0))) {
                    Life_Item li = (Life_Item) itens.get(i);
                    li.start();
                    itens.remove(i);
                    personagem.get(0).addVida(1);

                }
            } else if (itens.get(i) instanceof Energy_Item) {

                if (itens.get(i).itemDelay()) {
                    itens.remove(i);
                } else if (itens.get(i).colisao(personagem.get(0))) {
                    itens.remove(i);
                }
                personagem.get(0).addEnergia(15);
            }
        }

        //Itens extras
        Random r = new Random();
        if (r.nextInt(
                1000) % 377 == 0) {
            itens.add(PieceFactory.fabricaPeca(this, PieceType.PLUS_LIFE));
        }
        if (r.nextInt(
                1000) % 647 == 0) {
            itens.add(PieceFactory.fabricaPeca(this, PieceType.ENERGY));
        }

    }

    /**
     * Funcao que faz toda a checagem dos invasores. A colisao entre invasores é
     * feita nessa funcao. Tambem é feita a implementacao dos tiros que os invasores
     * realizam.
     */
    private void invaderUpdate() {

        for (int i = 0; i < invasor.size(); i++) {
            Point aux = invasor.get(i).getPosition();

            //teste de colisao
            invasor.get(i).deslocamento();
            for (int j = i; j < invasor.size(); j++) {

                if (i != j) {
                    if (invasor.get(i).colisao(invasor.get(j))) {
                        invasor.get(i).setPosition(aux);
                    }
                }
            }
            //Tiros dos invasores
            if (invasor.get(i) instanceof Invasor_Medium) {
                Invasor_Medium m = (Invasor_Medium) invasor.get(i);
                if (m.VerifyPlayer(personagem.get(0))) {
                    if (m.tiroDelay()) {
                        int k = itens.size();
                        itens.add(m.atira());
                        itens.get(k).setposicao(new Point(m.getPosition().x + 12, m.getPosition().y + 17));
                    }
                }

            } else if (invasor.get(i) instanceof Invasor_Hard) {
                Invasor_Hard h = (Invasor_Hard) invasor.get(i);
                if (h.VerifyPlayer(personagem.get(0))) {
                    if (h.tiroDelay()) {
                        int k = itens.size();
                        itens.add(h.atira());
                        itens.get(k).setposicao(new Point(h.getPosition().x + 12, h.getPosition().y + 17));
                    }
                }
            }
        }
    }

    /**
     * Funcao responsavel por aumentar o nivel da fase, inserindo mais invasores.
     * Cada nivel tem um numero pre definido de invasores de cada level.<br>
     * O nivel da fase é incremetado cada vez que se elimina uma quantidade
     * razoavel de invasores da fase.
     */
    private void levelUpdate() {
        if (elim >= (level + 1) * 2) {

            if (elimAux == 0) {
                inicializaInvasor((level + 1) * 2, TipoPersonagem.INVASOR_EASY);
                elimAux++;
                invasor.addAll(array);
                array.removeAll(invasor);
            } else if (elimAux == 6) {
                inicializaInvasor((level + 2), TipoPersonagem.INVASOR_MEDIUM);
                elimAux++;
                invasor.addAll(array);
                array.removeAll(invasor);
            } else if (elimAux == 12) {
                inicializaInvasor(level, TipoPersonagem.INVASOR_HARD);
                elimAux++;
                invasor.addAll(array);
                array.removeAll(invasor);
            } else if (elimAux >= 18) {
                for (Invasor i : invasor) {
                    i.desce();
                }
                elimAux = 0;
                elim = 0;
                level++;
            } else {
                for (Invasor i : invasor) {
                    i.desce();
                }
                elimAux++;
            }
        }
    }
}
